package com.gzc.just.play.last.war.common.share.distributed;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Server registry
 * Manages registration and discovery of server instances
 */
@Component
@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnProperty(name = "allinone.enable-redis", havingValue = "true", matchIfMissing = true)
public class ServerRegistry {

    private static final Logger log = LoggerFactory.getLogger(ServerRegistry.class);

    private final RedisTemplate<String, Object> redisTemplate;
    
    @Autowired(required = false)
    public ServerRegistry(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    @Value("${spring.application.name:default-service}")
    private String currentServiceName;
    
    @Value("${server.port:8080}")
    private int currentPort;
    
    // Server information cache
    private final Map<String, List<ServerInstance>> serviceCache = new ConcurrentHashMap<>();
    
    // Current server instance
    private ServerInstance currentInstance;
    
    // Redis key prefix
    private static final String SERVER_REGISTRY_KEY = "server_registry:";
    private static final String HEALTH_CHECK_KEY = "health_check:";
    
    /**
     * Initialize
     */
    @PostConstruct
    public void init() {
        // 如果 redisTemplate 为 null,跳过初始化
        if (redisTemplate == null) {
            log.info("Redis is not available, skipping ServerRegistry initialization");
            return;
        }
        
        try {
            log.info("Initializing server registry for service: {}", currentServiceName);
            
            // Create current server instance
            String host = InetAddress.getLocalHost().getHostAddress();
            String instanceId = currentServiceName + "-" + host + "-" + currentPort;
            currentInstance = new ServerInstance(
                    instanceId, currentServiceName, host, currentPort,
                    new HashMap<>()
            );
            currentInstance.setRegistrationTime(System.currentTimeMillis());
            
            // Register current service
            registerService(currentInstance);
            
            // Initialize load service information
            refreshServiceCache();
            
            log.info("Registered current service: {} - {}:{}", 
                    currentServiceName, host, currentPort);
        } catch (Exception e) {
            log.error("Error initializing server registry", e);
        }
    }
    
    /**
     * Register service
     */
    public void registerService(ServerInstance instance) {
        if (redisTemplate == null) {
            return;
        }
        
        String key = SERVER_REGISTRY_KEY + instance.getServiceName() + ":" + instance.getInstanceId();
        
        // 构建服务器信息
        Map<String, Object> serviceInfo = new HashMap<>();
        serviceInfo.put("instanceId", instance.getInstanceId());
        serviceInfo.put("serviceName", instance.getServiceName());
        serviceInfo.put("host", instance.getHost());
        serviceInfo.put("port", instance.getPort());
        serviceInfo.put("metadata", instance.getMetadata());
        serviceInfo.put("registrationTime", instance.getRegistrationTime());
        serviceInfo.put("lastHeartbeat", System.currentTimeMillis());
        
        // Save to Redis (with TTL)
        redisTemplate.opsForHash().putAll(key, serviceInfo);
        redisTemplate.expire(key, 90, java.util.concurrent.TimeUnit.SECONDS); // 90 seconds TTL
        
        log.info("Registered service instance: {}", instance.getInstanceId());
    }
    
    /**
     * 注销服务
     */
    public void deregisterService(String serviceName, String instanceId) {
        if (redisTemplate == null) {
            return;
        }
        
        String key = SERVER_REGISTRY_KEY + serviceName + ":" + instanceId;
        redisTemplate.delete(key);
        
        // Remove from cache
        List<ServerInstance> instances = serviceCache.get(serviceName);
        if (instances != null) {
            instances.removeIf(instance -> instance.getInstanceId().equals(instanceId));
        }
        
        log.info("Deregistered service instance: {}", instanceId);
    }
    
    /**
     * Periodically refresh service cache
     */
    @Scheduled(fixedRate = 30000) // Execute every 30 seconds
    public void refreshServiceCache() {
        if (redisTemplate == null) {
            return;
        }
        
        try {
            // Scan all service keys
            Set<String> keys = redisTemplate.keys(SERVER_REGISTRY_KEY + "*");
            if (keys == null || keys.isEmpty()) {
                return;
            }
            
            Map<String, List<ServerInstance>> newCache = new HashMap<>();
            
            for (String key : keys) {
                try {
                    Map<Object, Object> serviceData = redisTemplate.opsForHash().entries(key);
                    if (!serviceData.isEmpty()) {
                        // Parse service information
                        String serviceName = (String) serviceData.get("serviceName");
                        String instanceId = (String) serviceData.get("instanceId");
                        String host = (String) serviceData.get("host");
                        Integer port = (Integer) serviceData.get("port");
                        @SuppressWarnings("unchecked")
                        Map<String, String> metadata = (Map<String, String>) serviceData.get("metadata");
                        Long registrationTime = (Long) serviceData.get("registrationTime");
                        
                        ServerInstance instance = new ServerInstance(
                                instanceId,
                                serviceName,
                                host,
                                port,
                                metadata != null ? metadata : new HashMap<>()
                        );
                        if (registrationTime != null) {
                            instance.setRegistrationTime(registrationTime);
                        }
                        
                        newCache.computeIfAbsent(serviceName, k -> new ArrayList<>()).add(instance);
                    }
                } catch (Exception e) {
                    log.error("Error parsing service key: {}", key, e);
                }
            }
            
            // Update cache
            serviceCache.clear();
            serviceCache.putAll(newCache);
            
            log.debug("Refreshed service cache, total services: {}", newCache.size());
        } catch (Exception e) {
            log.error("Error refreshing service cache", e);
        }
    }
    
    /**
     * Get service instance list
     */
    public List<ServerInstance> getInstances(String serviceName) {
        // First get from local cache
        List<ServerInstance> instances = serviceCache.get(serviceName);
        if (instances != null && !instances.isEmpty()) {
            return new ArrayList<>(instances);
        }
        
        // If not in local cache, get from Redis
        refreshServiceCache();
        instances = serviceCache.get(serviceName);
        
        return instances != null ? new ArrayList<>(instances) : new ArrayList<>();
    }
    
    /**
     * Get service instance
     */
    public ServerInstance getInstance(String serviceName, String instanceId) {
        List<ServerInstance> instances = getInstances(serviceName);
        return instances.stream()
                .filter(instance -> instance.getInstanceId().equals(instanceId))
                .findFirst()
                .orElse(null);
    }
    
    /**
     * Send health heartbeat
     */
    @Scheduled(fixedRate = 10000) // Execute every 10 seconds
    public void sendHealthCheck() {
        if (redisTemplate == null) {
            return;
        }
        
        try {
            if (currentInstance == null) {
                return;
            }
            
            // Update heartbeat time of current instance
            String key = SERVER_REGISTRY_KEY + currentInstance.getServiceName() + ":" + currentInstance.getInstanceId();
            redisTemplate.opsForHash().put(key, "lastHeartbeat", System.currentTimeMillis());
            redisTemplate.expire(key, 90, java.util.concurrent.TimeUnit.SECONDS);
            
            // Update health check key
            String healthKey = HEALTH_CHECK_KEY + currentInstance.getServiceName() + ":" + currentInstance.getPort();
            redisTemplate.opsForValue().set(healthKey, System.currentTimeMillis());
            redisTemplate.expire(healthKey, 30, java.util.concurrent.TimeUnit.SECONDS);
            
            log.debug("Sent health check for: {}:{}", currentInstance.getServiceName(), currentInstance.getPort());
        } catch (Exception e) {
            log.error("Error sending health check", e);
        }
    }
    
    /**
     * Check if service is healthy
     */
    public boolean isServiceHealthy(String serviceName, int port) {
        if (redisTemplate == null) {
            return true; // 没有 Redis 时默认健康
        }
        
        String key = HEALTH_CHECK_KEY + serviceName + ":" + port;
        Long lastHeartbeat = (Long) redisTemplate.opsForValue().get(key);
        
        if (lastHeartbeat == null) {
            return false;
        }
        
        // If there's a heartbeat within 30 seconds, consider it healthy
        return (System.currentTimeMillis() - lastHeartbeat) < 30000;
    }
    
    /**
     * Get service health status
     */
    public Map<String, Boolean> getServiceHealthStatus() {
        Map<String, Boolean> healthStatus = new HashMap<>();
        
        for (Map.Entry<String, List<ServerInstance>> entry : serviceCache.entrySet()) {
            String serviceName = entry.getKey();
            for (ServerInstance instance : entry.getValue()) {
                String key = serviceName + ":" + instance.getPort();
                boolean healthy = isServiceHealthy(serviceName, instance.getPort());
                healthStatus.put(key, healthy);
            }
        }
        
        return healthStatus;
    }
    
    /**
     * Get service statistics
     */
    public Map<String, Object> getServiceStats() {
        Map<String, Object> stats = new HashMap<>();
        
        stats.put("totalServices", serviceCache.size());
        
        Map<String, Integer> serviceInstanceCount = new HashMap<>();
        for (Map.Entry<String, List<ServerInstance>> entry : serviceCache.entrySet()) {
            serviceInstanceCount.put(entry.getKey(), entry.getValue().size());
        }
        stats.put("serviceInstanceCount", serviceInstanceCount);
        
        // Get health status statistics
        Map<String, Boolean> healthStatus = getServiceHealthStatus();
        long healthyCount = healthStatus.values().stream().mapToLong(healthy -> healthy ? 1 : 0).sum();
        stats.put("healthyInstances", healthyCount);
        stats.put("totalInstances", healthStatus.size());
        
        return stats;
    }
    
    /**
     * Get current service instance
     */
    public ServerInstance getCurrentInstance() {
        return currentInstance;
    }
    
    /**
     * Select a service instance based on load balancing strategy
     */
    public ServerInstance selectInstance(String serviceName) {
        List<ServerInstance> instances = getInstances(serviceName);
        if (instances.isEmpty()) {
            return null;
        }
        
        // Simple random selection
        return instances.get((int) (Math.random() * instances.size()));
    }
}